/**
 * Title :	jFamilyTree generator
 * Description: jFamilyTree generator
 * Version:	provider
 * Copyright (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.generator;

import java.awt.Color;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import jFamilyTree.generator.tree.EdgesWrapper;
import jFamilyTree.generator.tree.MenuItemWrapper;
import jFamilyTree.generator.tree.NodesWrapper;
import jFamilyTree.generator.tree.TopWrapper;
import jFamilyTree.generator.tree.TreeWrapper;
import jFamilyTree.model.Edge;
import jFamilyTree.model.INode;
import jFamilyTree.model.MenuItem;
import jFamilyTree.model.MountPoint;
import jFamilyTree.model.MutableNode;
import jFamilyTree.model.Settings;
import jFamilyTree.model.TreeMutableEdge;
import jFamilyTree.model.TreeMutableNode;
import jFamilyTree.model.Utils;
import bsys.propertyview.PickListener;

/**
 * Property view for jFamilyTree documents
 * 
 * @author Bernard Bou
 */
public class PropertyView extends bsys.propertyview.PropertyView implements PickListener
{
	private static final long serialVersionUID = 1L;

	/**
	 * Id to node map
	 */
	private Map<String, MutableNode> theIdToNodeMap;

	/**
	 * The per-class handler map
	 */
	private Map<Class<?>, Handler> theClassHandler;

	/**
	 * Whether data have been modified
	 */
	public boolean dirty;

	// N O D E

	/**
	 * Node getter
	 */
	private static class NodeGetter implements Getter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Getter#get(java.lang.Object, java.lang.String)
		 */
		public Object get(Object thisObject, String thisPropertyName)
		{
			MutableNode thisNode = (MutableNode) thisObject;
			if (thisPropertyName.equals("id")) //$NON-NLS-1$
				return thisNode.getId();
			else if (thisPropertyName.equals("label")) //$NON-NLS-1$
				return thisNode.getLabel();
			else if (thisPropertyName.equals("content")) //$NON-NLS-1$
				return thisNode.getContent();
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				return thisNode.getImageFile();
			else if (thisPropertyName.equals("backcolor")) //$NON-NLS-1$
				return thisNode.getBackColor();
			else if (thisPropertyName.equals("forecolor")) //$NON-NLS-1$
				return thisNode.getForeColor();
			else if (thisPropertyName.equals("edge.color")) //$NON-NLS-1$
				return thisNode.getEdgeColor();
			else if (thisPropertyName.equals("edge.stroke")) //$NON-NLS-1$
				return Utils.toString(thisNode.getEdgeStyle(), Utils.StyleComponent.STROKE);
			else if (thisPropertyName.equals("edge.fromterminator")) //$NON-NLS-1$
				return Utils.toString(thisNode.getEdgeStyle(), Utils.StyleComponent.FROMTERMINATOR);
			else if (thisPropertyName.equals("edge.toterminator")) //$NON-NLS-1$
				return Utils.toString(thisNode.getEdgeStyle(), Utils.StyleComponent.TOTERMINATOR);
			else if (thisPropertyName.equals("edge.hidden")) //$NON-NLS-1$
				return Utils.toTrueBoolean(thisNode.getEdgeStyle(), Utils.StyleComponent.HIDDEN);
			else if (thisPropertyName.equals("edge.label")) //$NON-NLS-1$
				return thisNode.getEdgeLabel();
			else if (thisPropertyName.equals("edge.image")) //$NON-NLS-1$
				return thisNode.getEdgeImageFile();
			else if (thisPropertyName.equals("link")) //$NON-NLS-1$
				return thisNode.getLink();
			else if (thisPropertyName.equals("mount.url")) //$NON-NLS-1$
			{
				MountPoint.Mounting thisMountingPoint = (MountPoint.Mounting) thisNode.getMountPoint();
				return thisMountingPoint == null ? null : thisMountingPoint.theURL;
			}
			else if (thisPropertyName.equals("mount.now")) //$NON-NLS-1$
			{
				MountPoint.Mounting thisMountingPoint = (MountPoint.Mounting) thisNode.getMountPoint();
				return thisMountingPoint == null ? null : thisMountingPoint.now;
			}
			else if (thisPropertyName.equals("weight")) //$NON-NLS-1$
			{
				double thisWeight = thisNode.getWeight();
				return thisWeight < 0. ? Math.abs(thisWeight) : null;
			}
			System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
			return null;
		}
	};

	/**
	 * Node setter
	 */
	private class NodeSetter implements Setter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Setter#set(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		public void set(Object thisObject, String thisPropertyName, Object thisPropertyValue)
		{
			dirty = true;
			TreeMutableNode thisNode = (TreeMutableNode) thisObject;
			if (thisPropertyName.equals("id")) //$NON-NLS-1$
			{
				String thisId = (String) thisPropertyValue;
				String thatId = thisNode.getId();
				theIdToNodeMap.remove(thatId);
				theIdToNodeMap.put(thisId, thisNode);
				thisNode.setId(thisId);
			}
			else if (thisPropertyName.equals("label")) //$NON-NLS-1$
				thisNode.setLabel((String) thisPropertyValue);
			else if (thisPropertyName.equals("content")) //$NON-NLS-1$
				thisNode.setContent((String) thisPropertyValue);
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				thisNode.setImageFile((String) thisPropertyValue);
			else if (thisPropertyName.equals("backcolor")) //$NON-NLS-1$
				thisNode.setBackColor((Color) thisPropertyValue);
			else if (thisPropertyName.equals("forecolor")) //$NON-NLS-1$
				thisNode.setForeColor((Color) thisPropertyValue);
			else if (thisPropertyName.equals("edge.color")) //$NON-NLS-1$
				thisNode.setEdgeColor((Color) thisPropertyValue);
			else if (thisPropertyName.equals("edge.stroke")) //$NON-NLS-1$
				thisNode.setEdgeStyle(Utils.modifyStyle(thisNode.getEdgeStyle(), thisPropertyValue, Utils.StyleComponent.STROKE));
			else if (thisPropertyName.equals("edge.fromterminator")) //$NON-NLS-1$
				thisNode.setEdgeStyle(Utils.modifyStyle(thisNode.getEdgeStyle(), thisPropertyValue, Utils.StyleComponent.FROMTERMINATOR));
			else if (thisPropertyName.equals("edge.toterminator")) //$NON-NLS-1$
				thisNode.setEdgeStyle(Utils.modifyStyle(thisNode.getEdgeStyle(), thisPropertyValue, Utils.StyleComponent.TOTERMINATOR));
			else if (thisPropertyName.equals("edge.hidden")) //$NON-NLS-1$
				thisNode.setEdgeStyle(Utils.modifyStyle(thisNode.getEdgeStyle(), thisPropertyValue, Utils.StyleComponent.HIDDEN));
			else if (thisPropertyName.equals("edge.label")) //$NON-NLS-1$
				thisNode.setEdgeLabel((String) thisPropertyValue);
			else if (thisPropertyName.equals("edge.image")) //$NON-NLS-1$
				thisNode.setEdgeImageFile((String) thisPropertyValue);
			else if (thisPropertyName.equals("link")) //$NON-NLS-1$
				thisNode.setLink((String) thisPropertyValue);
			else if (thisPropertyName.equals("mount.url")) //$NON-NLS-1$
			{
				if (thisPropertyValue != null)
				{
					MountPoint.Mounting thisMountingPoint = (MountPoint.Mounting) thisNode.getMountPoint();
					if (thisMountingPoint == null)
					{
						thisMountingPoint = new MountPoint.Mounting();
						thisNode.setMountPoint(thisMountingPoint);
					}
					thisMountingPoint.theURL = (String) thisPropertyValue;
				}
				else
					thisNode.setMountPoint(null);
			}
			else if (thisPropertyName.equals("mount.now")) //$NON-NLS-1$
			{
				if (thisPropertyValue != null)
				{
					MountPoint.Mounting thisMountingPoint = (MountPoint.Mounting) thisNode.getMountPoint();
					if (thisMountingPoint != null)
						thisMountingPoint.now = (Boolean) thisPropertyValue;
				}
			}
			else if (thisPropertyName.equals("weight")) //$NON-NLS-1$
			{
				Double thisWeight = (Double) thisPropertyValue;
				if (thisWeight != null)
					thisNode.setWeight(-thisWeight);
			}
			else
				System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
		}
	}

	// E D G E

	/**
	 * Edge getter
	 */
	private class EdgeGetter implements Getter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Getter#get(java.lang.Object, java.lang.String)
		 */
		public Object get(Object thisObject, String thisPropertyName)
		{
			Edge thisEdge = (Edge) thisObject;
			if (thisPropertyName.equals("from")) //$NON-NLS-1$
			{
				INode thisNode = thisEdge.getFrom();
				return thisNode == null ? null : thisNode.getId();
			}
			else if (thisPropertyName.equals("to")) //$NON-NLS-1$
			{
				INode thisNode = thisEdge.getTo();
				return thisNode == null ? null : thisNode.getId();
			}
			else if (thisPropertyName.equals("label")) //$NON-NLS-1$
				return thisEdge.getLabel();
			else if (thisPropertyName.equals("color")) //$NON-NLS-1$
				return thisEdge.getColor();
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				return thisEdge.getImageFile();
			else if (thisPropertyName.equals("stroke")) //$NON-NLS-1$
				return Utils.toString(thisEdge.getStyle(), Utils.StyleComponent.STROKE);
			else if (thisPropertyName.equals("from.terminator")) //$NON-NLS-1$
				return Utils.toString(thisEdge.getStyle(), Utils.StyleComponent.FROMTERMINATOR);
			else if (thisPropertyName.equals("to.terminator")) //$NON-NLS-1$
				return Utils.toString(thisEdge.getStyle(), Utils.StyleComponent.TOTERMINATOR);
			else if (thisPropertyName.equals("hidden")) //$NON-NLS-1$
				return Utils.toTrueBoolean(thisEdge.getStyle(), Utils.StyleComponent.HIDDEN);
			System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
			return null;
		}
	}

	/**
	 * Edge setter
	 */
	private class EdgeSetter implements Setter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Setter#set(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		public void set(Object thisObject, String thisPropertyName, Object thisPropertyValue)
		{
			dirty = true;
			TreeMutableEdge thisEdge = (TreeMutableEdge) thisObject;
			if (thisPropertyName.equals("from")) //$NON-NLS-1$
			{
				String thisId = (String) thisPropertyValue;
				MutableNode thisNode = thisId == null ? null : theIdToNodeMap.get(thisId);
				thisEdge.setFrom(thisNode);
			}
			else if (thisPropertyName.equals("to")) //$NON-NLS-1$
			{
				String thisId = (String) thisPropertyValue;
				MutableNode thisNode = thisId == null ? null : theIdToNodeMap.get(thisId);
				thisEdge.setTo(thisNode);
			}
			else if (thisPropertyName.equals("label")) //$NON-NLS-1$
				thisEdge.setLabel((String) thisPropertyValue);
			else if (thisPropertyName.equals("color")) //$NON-NLS-1$
				thisEdge.setColor((Color) thisPropertyValue);
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				thisEdge.setImageFile((String) thisPropertyValue);
			else if (thisPropertyName.equals("stroke")) //$NON-NLS-1$
				thisEdge.setStyle(Utils.modifyStyle(thisEdge.getStyle(), thisPropertyValue, Utils.StyleComponent.STROKE));
			else if (thisPropertyName.equals("from.terminator")) //$NON-NLS-1$
				thisEdge.setStyle(Utils.modifyStyle(thisEdge.getStyle(), thisPropertyValue, Utils.StyleComponent.FROMTERMINATOR));
			else if (thisPropertyName.equals("to.terminator")) //$NON-NLS-1$
				thisEdge.setStyle(Utils.modifyStyle(thisEdge.getStyle(), thisPropertyValue, Utils.StyleComponent.TOTERMINATOR));
			else if (thisPropertyName.equals("hidden")) //$NON-NLS-1$
				thisEdge.setStyle(Utils.modifyStyle(thisEdge.getStyle(), thisPropertyValue, Utils.StyleComponent.HIDDEN));
			else
				System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
		}
	}

	// T O P
	/**
	 * Top element setter
	 */

	private class TopGetter implements Getter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Getter#get(java.lang.Object, java.lang.String)
		 */
		public Object get(Object thisObject, String thisPropertyName)
		{
			TopWrapper theseTopSettings = (TopWrapper) thisObject;
			Settings theseSettings = theseTopSettings.theSettings;
			if (thisPropertyName.equals("toolbar")) //$NON-NLS-1$
				return theseSettings.theHasToolbarFlag;
			else if (thisPropertyName.equals("statusbar")) //$NON-NLS-1$
				return theseSettings.theHasStatusbarFlag;
			else if (thisPropertyName.equals("popup.menu")) //$NON-NLS-1$
				return theseSettings.theHasPopUpMenuFlag;
			else if (thisPropertyName.equals("tooltip")) //$NON-NLS-1$
				return theseSettings.theHasToolTipFlag;
			else if (thisPropertyName.equals("tooltip.displays.content")) //$NON-NLS-1$
				return theseSettings.theToolTipDisplaysContentFlag;
			else if (thisPropertyName.equals("focus.on.hover")) //$NON-NLS-1$
				return theseSettings.theFocusOnHoverFlag;
			else if (thisPropertyName.equals("focus")) //$NON-NLS-1$
				return theseSettings.theFocus;
			else if (thisPropertyName.equals("x.moveto")) //$NON-NLS-1$
				return theseSettings.theXMoveTo;
			else if (thisPropertyName.equals("y.moveto")) //$NON-NLS-1$
				return theseSettings.theYMoveTo;
			else if (thisPropertyName.equals("x.shift")) //$NON-NLS-1$
				return theseSettings.theXShift;
			else if (thisPropertyName.equals("y.shift")) //$NON-NLS-1$
				return theseSettings.theYShift;
			System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
			return null;
		}

	}

	/**
	 * Top element setter
	 */
	private class TopSetter implements Setter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Setter#set(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		public void set(Object thisObject, String thisPropertyName, Object thisPropertyValue)
		{
			dirty = true;
			TopWrapper theseTopSettings = (TopWrapper) thisObject;
			Settings theseSettings = theseTopSettings.theSettings;
			if (thisPropertyName.equals("toolbar")) //$NON-NLS-1$
				theseSettings.theHasToolbarFlag = (Boolean) thisPropertyValue;
			else if (thisPropertyName.equals("statusbar")) //$NON-NLS-1$
				theseSettings.theHasStatusbarFlag = (Boolean) thisPropertyValue;
			else if (thisPropertyName.equals("popup.menu")) //$NON-NLS-1$
				theseSettings.theHasPopUpMenuFlag = (Boolean) thisPropertyValue;
			else if (thisPropertyName.equals("tooltip")) //$NON-NLS-1$
				theseSettings.theHasToolTipFlag = (Boolean) thisPropertyValue;
			else if (thisPropertyName.equals("tooltip.displays.content")) //$NON-NLS-1$
				theseSettings.theToolTipDisplaysContentFlag = (Boolean) thisPropertyValue;
			else if (thisPropertyName.equals("focus")) //$NON-NLS-1$
				theseSettings.theFocus = (String) thisPropertyValue;
			else if (thisPropertyName.equals("focus.on.hover")) //$NON-NLS-1$
				theseSettings.theFocusOnHoverFlag = (Boolean) thisPropertyValue;
			else if (thisPropertyName.equals("x.moveto")) //$NON-NLS-1$
				theseSettings.theXMoveTo = (Double) thisPropertyValue;
			else if (thisPropertyName.equals("y.moveto")) //$NON-NLS-1$
				theseSettings.theYMoveTo = (Double) thisPropertyValue;
			else if (thisPropertyName.equals("x.shift")) //$NON-NLS-1$
				theseSettings.theXShift = (Double) thisPropertyValue;
			else if (thisPropertyName.equals("y.shift")) //$NON-NLS-1$
				theseSettings.theYShift = (Double) thisPropertyValue;
			else
				System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
		}
	}

	// T R E E

	/**
	 * Tree element setter
	 */
	private class TreeGetter implements Getter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Getter#get(java.lang.Object, java.lang.String)
		 */
		public Object get(Object thisObject, String thisPropertyName)
		{
			TreeWrapper theseTreeSettings = (TreeWrapper) thisObject;
			Settings theseSettings = theseTreeSettings.theSettings;
			if (thisPropertyName.equals("background.image")) //$NON-NLS-1$
				return theseSettings.theBackgroundImage;
			else if (thisPropertyName.equals("orientation")) //$NON-NLS-1$
				return theseSettings.theOrientation;
			else if (thisPropertyName.equals("expansion")) //$NON-NLS-1$
				return theseSettings.theExpansion;
			else if (thisPropertyName.equals("sweep")) //$NON-NLS-1$
				return theseSettings.theSweep;
			else if (thisPropertyName.equals("forecolor")) //$NON-NLS-1$
				return theseSettings.theForeColor;
			else if (thisPropertyName.equals("backcolor")) //$NON-NLS-1$
				return theseSettings.theBackColor;
			else if (thisPropertyName.equals("font.face")) //$NON-NLS-1$
				return theseSettings.theFontFace;
			else if (thisPropertyName.equals("font.size")) //$NON-NLS-1$
				return theseSettings.theFontSize;
			else if (thisPropertyName.equals("font.size.step")) //$NON-NLS-1$
				return theseSettings.theFontSizeStep;
			else if (thisPropertyName.equals("preserve.orientation")) //$NON-NLS-1$
				return theseSettings.thePreserveOrientationFlag;
			System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
			return null;
		}
	}

	/**
	 * Tree element setter
	 */
	private class TreeSetter implements Setter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Setter#set(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		public void set(Object thisObject, String thisPropertyName, Object thisPropertyValue)
		{
			dirty = true;
			TreeWrapper theseTreeSettings = (TreeWrapper) thisObject;
			Settings theseSettings = theseTreeSettings.theSettings;
			if (thisPropertyName.equals("background.image")) //$NON-NLS-1$
				theseSettings.theBackgroundImage = (String) thisPropertyValue;
			else if (thisPropertyName.equals("orientation")) //$NON-NLS-1$
				theseSettings.theOrientation = (String) thisPropertyValue;
			else if (thisPropertyName.equals("expansion")) //$NON-NLS-1$
				theseSettings.theExpansion = (Double) thisPropertyValue;
			else if (thisPropertyName.equals("sweep")) //$NON-NLS-1$
				theseSettings.theSweep = (Double) thisPropertyValue;
			else if (thisPropertyName.equals("forecolor")) //$NON-NLS-1$
				theseSettings.theForeColor = (Color) thisPropertyValue;
			else if (thisPropertyName.equals("backcolor")) //$NON-NLS-1$
				theseSettings.theBackColor = (Color) thisPropertyValue;
			else if (thisPropertyName.equals("font.face")) //$NON-NLS-1$
				theseSettings.theFontFace = (String) thisPropertyValue;
			else if (thisPropertyName.equals("font.size")) //$NON-NLS-1$
				theseSettings.theFontSize = (Integer) thisPropertyValue;
			else if (thisPropertyName.equals("font.size.step")) //$NON-NLS-1$
				theseSettings.theFontSizeStep = (Integer) thisPropertyValue;
			else if (thisPropertyName.equals("preserve.orientation")) //$NON-NLS-1$
				theseSettings.thePreserveOrientationFlag = (Boolean) thisPropertyValue;
			else
				System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
		}
	}

	// N O D E S

	/**
	 * Nodes getter
	 */
	private class NodesGetter implements Getter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Getter#get(java.lang.Object, java.lang.String)
		 */
		public Object get(Object thisObject, String thisPropertyName)
		{
			NodesWrapper theseNodesSettings = (NodesWrapper) thisObject;
			Settings theseSettings = theseNodesSettings.theSettings;
			if (thisPropertyName.equals("backcolor")) //$NON-NLS-1$
				return theseSettings.theNodeBackColor;
			else if (thisPropertyName.equals("forecolor")) //$NON-NLS-1$
				return theseSettings.theNodeForeColor;
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				return theseSettings.theDefaultNodeImage;
			else if (thisPropertyName.equals("edge.color")) //$NON-NLS-1$
				return theseSettings.theTreeEdgeColor;
			else if (thisPropertyName.equals("edge.from.terminator")) //$NON-NLS-1$
				return Utils.toString(theseSettings.theTreeEdgeStyle, Utils.StyleComponent.FROMTERMINATOR);
			else if (thisPropertyName.equals("edge.to.terminator")) //$NON-NLS-1$
				return Utils.toString(theseSettings.theTreeEdgeStyle, Utils.StyleComponent.TOTERMINATOR);
			else if (thisPropertyName.equals("edge.stroke")) //$NON-NLS-1$
				return Utils.toString(theseSettings.theTreeEdgeStyle, Utils.StyleComponent.STROKE);
			else if (thisPropertyName.equals("edge.hidden")) //$NON-NLS-1$
				return Utils.toTrueBoolean(theseSettings.theTreeEdgeStyle, Utils.StyleComponent.HIDDEN);
			else if (thisPropertyName.equals("edge.image")) //$NON-NLS-1$
				return theseSettings.theDefaultTreeEdgeImage;
			System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
			return null;
		}
	}

	/**
	 * Nodes setter
	 */
	private class NodesSetter implements Setter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Setter#set(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		public void set(Object thisObject, String thisPropertyName, Object thisPropertyValue)
		{
			dirty = true;
			NodesWrapper theseNodesSettings = (NodesWrapper) thisObject;
			Settings theseSettings = theseNodesSettings.theSettings;
			if (thisPropertyName.equals("backcolor")) //$NON-NLS-1$
				theseSettings.theNodeBackColor = (Color) thisPropertyValue;
			else if (thisPropertyName.equals("forecolor")) //$NON-NLS-1$
				theseSettings.theNodeForeColor = (Color) thisPropertyValue;
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				theseSettings.theDefaultNodeImage = (String) thisPropertyValue;
			// tree edge
			else if (thisPropertyName.equals("edge.color")) //$NON-NLS-1$
				theseSettings.theTreeEdgeColor = (Color) thisPropertyValue;
			else if (thisPropertyName.equals("edge.from.terminator")) //$NON-NLS-1$
				theseSettings.theTreeEdgeStyle = Utils.modifyStyle(theseSettings.theTreeEdgeStyle, thisPropertyValue, Utils.StyleComponent.FROMTERMINATOR);
			else if (thisPropertyName.equals("edge.to.terminator")) //$NON-NLS-1$
				theseSettings.theTreeEdgeStyle = Utils.modifyStyle(theseSettings.theTreeEdgeStyle, thisPropertyValue, Utils.StyleComponent.TOTERMINATOR);
			else if (thisPropertyName.equals("edge.stroke")) //$NON-NLS-1$
				theseSettings.theTreeEdgeStyle = Utils.modifyStyle(theseSettings.theTreeEdgeStyle, thisPropertyValue, Utils.StyleComponent.STROKE);
			else if (thisPropertyName.equals("edge.hidden")) //$NON-NLS-1$
				theseSettings.theTreeEdgeStyle = Utils.modifyStyle(theseSettings.theTreeEdgeStyle, thisPropertyValue, Utils.StyleComponent.HIDDEN);
			else if (thisPropertyName.equals("edge.image")) //$NON-NLS-1$
				theseSettings.theDefaultTreeEdgeImage = (String) thisPropertyValue;
			else
				System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
		}
	}

	// E D G E S

	/**
	 * Edges getter
	 */
	private class EdgesGetter implements Getter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Getter#get(java.lang.Object, java.lang.String)
		 */
		public Object get(Object thisObject, String thisPropertyName)
		{
			EdgesWrapper theseEdgesSettings = (EdgesWrapper) thisObject;
			Settings theseSettings = theseEdgesSettings.theSettings;
			if (thisPropertyName.equals("color")) //$NON-NLS-1$
				return theseSettings.theEdgeColor;
			else if (thisPropertyName.equals("from.terminator")) //$NON-NLS-1$
				return Utils.toString(theseSettings.theEdgeStyle, Utils.StyleComponent.FROMTERMINATOR);
			else if (thisPropertyName.equals("to.terminator")) //$NON-NLS-1$
				return Utils.toString(theseSettings.theEdgeStyle, Utils.StyleComponent.TOTERMINATOR);
			else if (thisPropertyName.equals("stroke")) //$NON-NLS-1$
				return Utils.toString(theseSettings.theEdgeStyle, Utils.StyleComponent.STROKE);
			else if (thisPropertyName.equals("hidden")) //$NON-NLS-1$
				return Utils.toTrueBoolean(theseSettings.theEdgeStyle, Utils.StyleComponent.HIDDEN);
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				return theseSettings.theDefaultEdgeImage;
			System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
			return null;
		}
	}

	/**
	 * Edges setter
	 */
	private class EdgesSetter implements Setter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Setter#set(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		public void set(Object thisObject, String thisPropertyName, Object thisPropertyValue)
		{
			dirty = true;
			EdgesWrapper theseEdgesSettings = (EdgesWrapper) thisObject;
			Settings theseSettings = theseEdgesSettings.theSettings;
			if (thisPropertyName.equals("color")) //$NON-NLS-1$
				theseSettings.theEdgeColor = (Color) thisPropertyValue;
			else if (thisPropertyName.equals("image")) //$NON-NLS-1$
				theseSettings.theDefaultEdgeImage = (String) thisPropertyValue;
			else if (thisPropertyName.equals("from.terminator")) //$NON-NLS-1$
				theseSettings.theEdgeStyle = Utils.modifyStyle(theseSettings.theEdgeStyle, thisPropertyValue, Utils.StyleComponent.FROMTERMINATOR);
			else if (thisPropertyName.equals("to.terminator")) //$NON-NLS-1$
				theseSettings.theEdgeStyle = Utils.modifyStyle(theseSettings.theEdgeStyle, thisPropertyValue, Utils.StyleComponent.TOTERMINATOR);
			else if (thisPropertyName.equals("stroke")) //$NON-NLS-1$
				theseSettings.theEdgeStyle = Utils.modifyStyle(theseSettings.theEdgeStyle, thisPropertyValue, Utils.StyleComponent.STROKE);
			else if (thisPropertyName.equals("hidden")) //$NON-NLS-1$
				theseSettings.theEdgeStyle = Utils.modifyStyle(theseSettings.theEdgeStyle, thisPropertyValue, Utils.StyleComponent.HIDDEN);
			else if (thisPropertyName.equals("hyperbolic")) //$NON-NLS-1$
				theseSettings.theHyperbolicEdgesFlag = (Boolean) thisPropertyValue;
			else
				System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
		}
	}

	// M E N U I T E M

	/**
	 * Menu element getter
	 */
	private class MenuItemGetter implements Getter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Getter#get(java.lang.Object, java.lang.String)
		 */
		public Object get(Object thisObject, String thisPropertyName)
		{
			MenuItemWrapper thisMenuItemWrapper = (MenuItemWrapper) thisObject;
			MenuItem thisMenuItem = thisMenuItemWrapper.theMenuItem;
			if (thisPropertyName.equals("label")) //$NON-NLS-1$
				return thisMenuItem.theLabel;
			else if (thisPropertyName.equals("link")) //$NON-NLS-1$
				return thisMenuItem.theLink;
			else if (thisPropertyName.equals("action")) //$NON-NLS-1$
				return Utils.toString(thisMenuItem.theAction);
			else if (thisPropertyName.equals("match.target")) //$NON-NLS-1$
				return thisMenuItem.theMatchTarget;
			else if (thisPropertyName.equals("match.scope")) //$NON-NLS-1$
				return Utils.toString(thisMenuItem.theMatchScope);
			else if (thisPropertyName.equals("match.mode")) //$NON-NLS-1$
				return Utils.toString(thisMenuItem.theMatchMode);
			System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
			return null;
		}
	}

	/**
	 * Menu element setter
	 */
	private class MenuItemSetter implements Setter
	{
		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.Setter#set(java.lang.Object, java.lang.String, java.lang.Object)
		 */
		public void set(Object thisObject, String thisPropertyName, Object thisPropertyValue)
		{
			dirty = true;
			MenuItemWrapper thisMenuItemWrapper = (MenuItemWrapper) thisObject;
			MenuItem thisMenuItem = thisMenuItemWrapper.theMenuItem;
			if (thisPropertyName.equals("label")) //$NON-NLS-1$
				thisMenuItem.theLabel = (String) thisPropertyValue;
			else if (thisPropertyName.equals("link")) //$NON-NLS-1$
				thisMenuItem.theLink = (String) thisPropertyValue;
			else if (thisPropertyName.equals("action")) //$NON-NLS-1$
				thisMenuItem.theAction = Utils.stringToAction((String) thisPropertyValue);
			else if (thisPropertyName.equals("match.scope")) //$NON-NLS-1$
				thisMenuItem.theMatchScope = Utils.stringToScope((String) thisPropertyValue);
			else if (thisPropertyName.equals("match.mode")) //$NON-NLS-1$
				thisMenuItem.theMatchMode = Utils.stringToMode((String) thisPropertyValue);
			else if (thisPropertyName.equals("match.target")) //$NON-NLS-1$
				thisMenuItem.theMatchTarget = (String) thisPropertyValue;
			else
				System.err.println("Unhandled property: " + thisPropertyName); //$NON-NLS-1$
		}
	}

	// I D

	/**
	 * Node id getter
	 */
	private class NodeIdGetter implements IdGetter
	{
		/**
		 * Id to node map
		 */
		private Map<String, MutableNode> theIdToNodeMap;

		/**
		 * Constructor
		 * 
		 * @param thisIdToNodeMap
		 *        id to node map
		 */
		public NodeIdGetter(Map<String, MutableNode> thisIdToNodeMap)
		{
			theIdToNodeMap = thisIdToNodeMap;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.IdGetter#get(java.lang.String)
		 */
		public Object get(String thisId)
		{
			return theIdToNodeMap.get(thisId);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see bsys.propertyview.PropertyView.IdGetter#ids()
		 */
		public Set<String> ids()
		{
			return theIdToNodeMap.keySet();
		}
	}

	/**
	 * Node features
	 */
	static final Object[][] theNodeFeatures = { //
	{ "id", PropertyView.AttributeType.ID, true, null }, // //$NON-NLS-1$
			{ "label", PropertyView.AttributeType.LABEL, false, null }, // //$NON-NLS-1$
			{ "content", PropertyView.AttributeType.LONGTEXT, false, null }, // //$NON-NLS-1$
			{ "backcolor", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "forecolor", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "image", PropertyView.AttributeType.IMAGE, false, null }, // //$NON-NLS-1$
			{ "edge.color", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "edge.stroke", PropertyView.AttributeType.STROKE, false, null }, // //$NON-NLS-1$
			{ "edge.fromterminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "edge.toterminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "edge.hidden", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "edge.label", PropertyView.AttributeType.TEXT, false, null }, // //$NON-NLS-1$
			{ "edge.image", PropertyView.AttributeType.IMAGE, false, null }, // //$NON-NLS-1$
			{ "link", PropertyView.AttributeType.LINK, false, null }, // //$NON-NLS-1$
			{ "mount.url", PropertyView.AttributeType.LINK, false, null }, // //$NON-NLS-1$
			{ "mount.now", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "weight", PropertyView.AttributeType.DOUBLE, false, null } // //$NON-NLS-1$
	};

	/**
	 * Edge features
	 */
	static final Object[][] theEdgeFeatures = { //
	{ "from", PropertyView.AttributeType.REFID, true, null }, //  //$NON-NLS-1$
			{ "to", PropertyView.AttributeType.REFID, true, null }, // //$NON-NLS-1$
			{ "color", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "stroke", PropertyView.AttributeType.STROKE, false, null }, // //$NON-NLS-1$
			{ "from.terminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "to.terminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "hidden", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "label", PropertyView.AttributeType.TEXT, false, null }, // //$NON-NLS-1$
			{ "image", PropertyView.AttributeType.IMAGE, false, null }, // //$NON-NLS-1$
	};

	/**
	 * Top element features
	 */
	static final Object[][] theTopFeatures = { //
	{ "toolbar", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "statusbar", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "popup.menu", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "tooltip", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "tooltip.displays.content", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "focus.on.hover", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "focus", PropertyView.AttributeType.REFID, false, null }, // //$NON-NLS-1$
			{ "x.moveto", PropertyView.AttributeType.DOUBLE, false, null }, // //$NON-NLS-1$
			{ "y.moveto", PropertyView.AttributeType.DOUBLE, false, null }, // //$NON-NLS-1$
			{ "x.shift", PropertyView.AttributeType.DOUBLE, false, null }, // //$NON-NLS-1$
			{ "y.shift", PropertyView.AttributeType.DOUBLE, false, null }, // //$NON-NLS-1$
	};

	/**
	 * Tree features
	 */
	static final Object[][] theTreeFeatures = { //
	{ "background.image", PropertyView.AttributeType.IMAGE, false, null }, // //$NON-NLS-1$
			{ "orientation", PropertyView.AttributeType.TEXT, false, new String[] { "radial", "north", "south", "east", "west" } }, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
			{ "expansion", PropertyView.AttributeType.DOUBLE, false, null }, //  //$NON-NLS-1$
			{ "sweep", PropertyView.AttributeType.DOUBLE, false, null }, // //$NON-NLS-1$
			{ "backcolor", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "forecolor", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "font.face", PropertyView.AttributeType.FONTFACE, false, null }, //  //$NON-NLS-1$
			{ "font.size", PropertyView.AttributeType.FONTSIZE, false, null }, // //$NON-NLS-1$
			{ "font.size.step", PropertyView.AttributeType.INTEGER, false, null }, // //$NON-NLS-1$
			{ "preserve.orientation", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
	};

	/**
	 * Nodes features
	 */
	static final Object[][] theNodesFeatures = { //
	{ "backcolor", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "forecolor", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "image", PropertyView.AttributeType.IMAGE, false, null }, // //$NON-NLS-1$
			{ "edge.color", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "edge.from.terminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "edge.to.terminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "edge.hidden", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "edge.stroke", PropertyView.AttributeType.STROKE, false, null }, // //$NON-NLS-1$
			{ "edge.image", PropertyView.AttributeType.IMAGE, false, null }, // //$NON-NLS-1$
	};

	/**
	 * Edges features
	 */
	static final Object[][] theEdgesFeatures = { //
	{ "color", PropertyView.AttributeType.COLOR, false, null }, // //$NON-NLS-1$
			{ "from.terminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "to.terminator", PropertyView.AttributeType.TERMINATOR, false, null }, // //$NON-NLS-1$
			{ "stroke", PropertyView.AttributeType.STROKE, false, null }, // //$NON-NLS-1$
			{ "hidden", PropertyView.AttributeType.BOOLEAN, false, null }, // //$NON-NLS-1$
			{ "image", PropertyView.AttributeType.IMAGE, false, null }, // //$NON-NLS-1$
	};

	/**
	 * Menu item features
	 */
	static final Object[][] theMenuItemFeatures = { //
	{ "label", PropertyView.AttributeType.TEXT, true, null }, // //$NON-NLS-1$
			{ "action", PropertyView.AttributeType.TEXT, true, new String[] { "goto", "search", "link", "focus", "mount" } }, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
			{ "match.target", PropertyView.AttributeType.TEXT, false, null }, // //$NON-NLS-1$
			{ "match.scope", PropertyView.AttributeType.TEXT, false, new String[] { "label", "content", "id", "link" } }, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
			{ "match.mode", PropertyView.AttributeType.TEXT, false, new String[] { "startswith", "includes", "equals" } }, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
			{ "link", PropertyView.AttributeType.LINK, false, null }, // //$NON-NLS-1$
	};

	/**
	 * Attribute comparator (rank)
	 */
	private Comparator<AttributeDescriptor> theComparator = new Comparator<AttributeDescriptor>()
	{
		/**
		 * @param thisDescriptor1
		 * @param thisDescriptor2
		 * @return
		 */
		public int compare(AttributeDescriptor thisDescriptor1, AttributeDescriptor thisDescriptor2)
		{
			return thisDescriptor1.theRank > thisDescriptor2.theRank ? 1 : 0;
		}
	};

	// C O N S T R U C T O R

	/**
	 * Constructor
	 */
	public PropertyView()
	{
		super();

		theClassHandler = makeHandlers();
		super.setHandlerFactory(makeHandlerFactory(theClassHandler));
	}

	// id map

	/**
	 * Set id to node map
	 * 
	 * @param thisIdToNodeMap
	 *        id to node map
	 */
	public void setIdToNodeMap(Map<String, MutableNode> thisIdToNodeMap)
	{
		theIdToNodeMap = thisIdToNodeMap;
		IdGetter thisIdGetter = new NodeIdGetter(theIdToNodeMap);
		theClassHandler.get(TreeMutableNode.class).theIdGetter = thisIdGetter;
		theClassHandler.get(TreeMutableEdge.class).theIdGetter = thisIdGetter;
		theClassHandler.get(TopWrapper.class).theIdGetter = thisIdGetter;
	}

	// select

	/**
	 * Make handler
	 * 
	 * @param theseFeatures
	 *        feature data
	 * @param thisGetter
	 *        getter
	 * @param thisSetter
	 *        setter
	 * @param thisIdGetter
	 *        id getter
	 * @return handler
	 */
	private Handler makeHandler(Object[][] theseFeatures, Getter thisGetter, Setter thisSetter, IdGetter thisIdGetter)
	{
		Handler thisHandler;
		thisHandler = new PropertyView.Handler();
		thisHandler.theAttributeDescriptors = new TreeSet<AttributeDescriptor>(theComparator);
		int thisRank = 0;
		for (Object[] thisFeature : theseFeatures)
		{
			String thisId = (String) thisFeature[0];
			AttributeDescriptor thisDescriptor = new AttributeDescriptor();
			thisDescriptor.theName = thisId;
			thisDescriptor.theType = (AttributeType) thisFeature[1];
			thisDescriptor.isMandatory = (Boolean) thisFeature[2];
			thisDescriptor.thePossibleValues = (String[]) thisFeature[3];
			thisDescriptor.isReadOnly = false;
			thisDescriptor.theRank = thisRank++;

			thisHandler.theAttributeDescriptors.add(thisDescriptor);
		}
		thisHandler.theGetter = thisGetter;
		thisHandler.theSetter = thisSetter;
		thisHandler.theIdGetter = thisIdGetter;
		return thisHandler;
	}

	/**
	 * Make handlers from feature tables
	 * 
	 * @return class to handler map
	 */
	private Map<Class<?>, Handler> makeHandlers()
	{
		Map<Class<?>, Handler> theseHandlers = new Hashtable<Class<?>, Handler>();
		theseHandlers.put(TreeMutableNode.class, makeHandler(theNodeFeatures, new NodeGetter(), new NodeSetter(), null));
		theseHandlers.put(TreeMutableEdge.class, makeHandler(theEdgeFeatures, new EdgeGetter(), new EdgeSetter(), null));
		theseHandlers.put(TopWrapper.class, makeHandler(theTopFeatures, new TopGetter(), new TopSetter(), null));
		theseHandlers.put(TreeWrapper.class, makeHandler(theTreeFeatures, new TreeGetter(), new TreeSetter(), null));
		theseHandlers.put(NodesWrapper.class, makeHandler(theNodesFeatures, new NodesGetter(), new NodesSetter(), null));
		theseHandlers.put(EdgesWrapper.class, makeHandler(theEdgesFeatures, new EdgesGetter(), new EdgesSetter(), null));
		theseHandlers.put(MenuItemWrapper.class, makeHandler(theMenuItemFeatures, new MenuItemGetter(), new MenuItemSetter(), null));
		return theseHandlers;
	}

	/**
	 * Make handler factory
	 * 
	 * @param thisHandlerMap
	 *        handler map
	 * @return handler factory
	 */
	public HandlerFactory makeHandlerFactory(final Map<Class<?>, Handler> thisHandlerMap)
	{
		return new HandlerFactory()
		{
			public Handler create(Object thisObject)
			{
				if (thisObject == null)
					return null;
				return thisHandlerMap.get(thisObject.getClass());
			}
		};
	}
}
